﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Medival_Snake
{
    class CNod
    {
        public CNod()
        {
            Pos = new Vector2(0, 0);
            nextNods = new List<CNod>();
        }

        ~CNod()
        {
            for (int g = 0; g < nextNods.Count; g++)
            {
                nextNods[g].Dispose();
            }

            nextNods.Clear();
        }

        public void Dispose()
        {
            //Finalize();
            System.GC.SuppressFinalize(this);
        }

        public CNod(Vector2 pos)
        {
            Pos = pos;
             nextNods = new List<CNod>();
        }
        
        public void AddNod(CNod newNod){
            nextNods.Add(newNod);
        }

        public Vector2 GetPos(){
            return Pos;
        }
        public int GetNextNodsCount(){
            return nextNods.Count;
        }

        public List<CNod> GetNextNods()
        {
            return nextNods;
        }

        public CNod GetNextNod(int index)
        {
            return nextNods[index];
        }

        Vector2 Pos;
        List<CNod> nextNods;


        public void Draw(SpriteBatch spriteBatch, Texture2D dot)
        {
            /*for (int i = 0; i < Nods.Count; i++ )
            {
                spriteBatch.Draw(dot, new Rectangle((int)Nods[i].GetPos().X, (int)Nods[i].GetPos().Y, 10,10), Color.Pink);
            }*/

            spriteBatch.Draw(dot, new Rectangle((int)Pos.X, (int)Pos.Y, 10, 10), Color.Pink);
            for (int i = 0; i < nextNods.Count; i++)
            {
                spriteBatch.Draw(dot, new Rectangle((int)nextNods[i].GetPos().X, (int)nextNods[i].GetPos().Y, 10, 10), Color.Black);
            }

            
            for (int i = 0; i < nextNods.Count; i++)
            {
                spriteBatch.Draw(dot, new Rectangle(10, 10 * i, 10, 10), Color.Black);
            }
        }
    }

    class Path
    {
        List<CNod> Nods = new List<CNod>();
        float cost = new float();

        public Path()
        {
        }

        ~Path()
        {
            for (int g = 0; g < Nods.Count; g++)
            {
                Nods[g].Dispose();
            }
            Nods.Clear();
        }

        public void Dispose()
        {
            //Finalize();
            System.GC.SuppressFinalize(this);
        }

        //criate with the first nod
        public Path(CNod firstNod)
        {
            Nods.Add(firstNod);
            cost = 0;
        }

        //copy construktor
        public Path(Path copy)
        {
            Nods = new List<CNod>(copy.Nods);
            cost = copy.cost;
        }

        //criate form existion path and add next nod
        public Path(Path copy, CNod next, float costToNext)
        {
            Nods = new List<CNod>(copy.Nods);
            cost = copy.cost;

            Nods.Add(next);
            cost += costToNext;
        }

        public void AddNod(CNod newNod, float costToNext)
        {
            Nods.Add(newNod);
            cost += costToNext;
        }

        public CNod getFinalNod(){
            return Nods[Nods.Count-1];
        }

        public void Draw(SpriteBatch spriteBatch, Texture2D dot)
        {
            for (int i = 0; i < Nods.Count; i++)
            {
                spriteBatch.Draw(dot, new Rectangle((int)Nods[i].GetPos().X, (int)Nods[i].GetPos().Y, 10, 10), new Color(i*10,i*20, 100));
            }
        }

        public float GetCost()
        {
            return cost;
        }
    }

    class AStar
    {

        List<CNod> Nods = new List<CNod>();

        Path testPath2 = new Path();

        public AStar()
        {
        }

        public void Update(){
        }

        public void GenerateNetworkFromMap(Map map)
        {
            CNod[,] tempNods = new CNod[map.MapHeight, map.MapWidth];


            //criate waypoints
            for(int x = 0; x < map.MapHeight; x++){
                for (int y = 0; y < map.MapWidth; y++)
                {
                    
                    
                    if (map.map[x, y] == 1)
                    {
                        tempNods[x, y] = new CNod(new Vector2((y * map.TileWidth) + (map.TileWidth / 2), (x * map.TileHeight)+(map.TileHeight/2)));
                    }
                    else
                    {
                        tempNods[x, y] = null;
                    }
                }
            }
            

            //criate links
            for (int y = 0; y < map.MapWidth; y++)
            {
                for (int x = 0; x < map.MapHeight; x++)
                {
                    int z = 0;
                    //up-left
                    if(tempNods[x,y] != null){
                        if ((((y - 1) > -1) && ((x - 1) > -1)) &&
                            tempNods[x - 1, y - 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x-1, y-1]);
                            z++;
                        }
                    }

                    //up
                    if (tempNods[x, y] != null)
                    {
                        if (((y - 1) > -1)  &&
                            tempNods[x, y - 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x, y - 1]);
                            z++;
                        }
                    }

                    //up-right
                    if (tempNods[x, y] != null)
                    {
                        if ((((y - 1) > -1) && ((x + 1) <= map.MapHeight)) &&
                            tempNods[x + 1, y - 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x + 1, y - 1]);
                            z++;
                        }
                    }


                    //down-left
                    if (tempNods[x, y] != null)
                    {
                        if ((((y + 1) <= map.MapWidth) && ((x - 1) > -1)) &&
                            tempNods[x - 1, y + 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x - 1, y + 1]);
                            z++;
                        }
                    }

                    //down
                    if (tempNods[x, y] != null)
                    {
                        if (((y + 1) <= map.MapWidth) &&
                            tempNods[x, y + 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x, y + 1]);
                            z++;
                        }
                    }

                    //down-right
                    if (tempNods[x, y] != null)
                    {
                        if ((((y + 1) <= map.MapWidth) && ((x + 1) <= map.MapHeight)) &&
                            tempNods[x + 1, y + 1] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x + 1, y + 1]);
                            z++;
                        }
                    }




                    //right
                    if (tempNods[x, y] != null)
                    {
                        if (((x + 1) <= map.MapHeight) &&
                            tempNods[x + 1, y] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x + 1, y]);
                            z++;
                        }
                    }


                    //left
                    if (tempNods[x, y] != null)
                    {
                        if (((x - 1) > -1) &&
                            tempNods[x - 1, y] != null)
                        {
                            tempNods[x, y].AddNod(tempNods[x - 1, y]);
                            z++;
                        }
                    }

                }
            }

            //add to list
            for (int y = 0; y < map.MapWidth; y++)
            {
                for (int x = 0; x < map.MapHeight; x++)
                {
                    if (tempNods[x, y] != null)
                    {
                        Nods.Add(tempNods[x, y]);
                    }
                }
            }
        }

        public Path FindPath(Vector2 Start, Vector2 Goal){
            
            
            int StartIndex = GetCloseclosestNod(Start);
            int GoalIndex = GetCloseclosestNod(Goal);

            List<CNod> discoveredNods = new List<CNod>();
            List<Path> Paths = new List<Path>();
            //List<Path> PathRecord = new List<Path>();


            discoveredNods.Add(Nods[StartIndex]);
            Paths.Add(new Path(Nods[StartIndex]));
            //PathRecord.Add(new Path(Nods[StartIndex]));
            
            int loopcount = 0;
            while (loopcount< 1000)
            {
                
                
                loopcount++;
                int lowestCostPathIndex = GetLowesCostPath(Paths);
                Path lowestCostPath = Paths[lowestCostPathIndex];
                CNod FinNodlowestCostPath = lowestCostPath.getFinalNod();

                
                
                for (int i = 0; i < FinNodlowestCostPath.GetNextNodsCount(); i++)
                {
                    
                    CNod NextNod = FinNodlowestCostPath.GetNextNod(i);

                    if (!IsThisNodExplored(NextNod, discoveredNods))
                    {

                        discoveredNods.Add(NextNod);

                        float G = Math2D.DistanceHscor(FinNodlowestCostPath.GetPos(), NextNod.GetPos());
                        float H = Math2D.DistanceHscor(FinNodlowestCostPath.GetPos(), Nods[GoalIndex].GetPos());
                        float F = G + H;

                        if (NextNod.GetHashCode() == Nods[GoalIndex].GetHashCode())
                        {
                            discoveredNods.Clear();

                            for (int g = 0; g < discoveredNods.Count; g++)
                            {
                                discoveredNods[g].Dispose();
                            }
                            Path returnPath = new Path(lowestCostPath, NextNod, F);
                            for (int g = 0; g < Paths.Count; g++)
                            {
                                Paths[g].Dispose();
                            }
                                Paths.Clear();

                            //PathRecord.Clear();
                                return returnPath;
                        }


                        Paths.Add(new Path(lowestCostPath, NextNod, F));
                        //PathRecord.Add(new Path(lowestCostPath, NextNod, F));

                    }
                    else
                    {
                        
                    }

                    
               }
                Paths[lowestCostPathIndex].Dispose();
                Paths.RemoveAt(lowestCostPathIndex);
                if (Paths.Count < 1)
                {
                    
                    break;
                }
            }


            return new Path();
        }

        public void AddNod(CNod newNod)
        {
            Nods.Add(newNod);
        }

        public void Draw(SpriteBatch spriteBatch, Texture2D dot, int lols)
        {
            //for (int i = 0; i < Nods.Count; i++ )
            //{
            //    spriteBatch.Draw(dot, new Rectangle((int)Nods[i].GetPos().X, (int)Nods[i].GetPos().Y, 10,10), Color.Pink);
            //}

            int index = GetCloseclosestNod(CMouse.Position);
            spriteBatch.Draw(dot, new Rectangle((int)Nods[index].GetPos().X, (int)Nods[index].GetPos().Y, 10, 10), Color.Black);


            if (CMouse.RightButton.Hold)
            {
                testPath2 = FindPath(CMouse.Position, new Vector2(0, 0));
                testPath2.Draw(spriteBatch, dot);
                testPath2.Dispose();
            }

        }


        //////////////////////ptivate methods //////////////////
        int GetCloseclosestNod(Vector2 pos){
            int index = 0;
            float distance = Math2D.DistanceHscor(pos, Nods[0].GetPos());

            float newDistance = 0;

            for (int i = 0; i < Nods.Count; i++ )
            {
                //TODO: fix memory leak
                newDistance = Math2D.DistanceHscor(pos, Nods[i].GetPos());
                if(newDistance < distance) {
                    distance = newDistance;
                    index = i;
                }
            }


            return index;
        }

        int GetLowesCostPath(List<Path> pathlist)
        {
            

            int lowestIndex = 0;
            float lowest = pathlist[0].GetCost();
            for (int i = 0; i < pathlist.Count; i++)
            {
                if (lowest > pathlist[i].GetCost())
                {
                    lowest = pathlist[i].GetCost();
                    lowestIndex = i;
                }
            }
            return lowestIndex;
        }

        bool IsThisNodExplored(CNod nod, List<CNod> exploredNods)
        {
            for (int i = 0; i < exploredNods.Count; i++)
            {
                if (exploredNods[i].GetHashCode() == nod.GetHashCode())
                    return true;
            }
            return false;
        }
    }
}
